﻿using SharpDepend.Datatypes;
using SoundEngine3.SequenceContent;
using System;
using System.Collections.Generic;
using System.Linq;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Commands;
using ZquenceStudio3Host.Commands.BuiltInCommands;
using ZquenceStudio3Host.Graphics.Components;
using ZquenceStudio3Host.Sequencer;
using ZquenceStudio3Host.TrackArea;
using ZquenceStudio3Host.TrackArea.Events;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI.Controls
{
    public class NodisizerAreaHandler : AreaHandler<TrackAreaArgs, ModulePanel, BasicSequenceState>
    {
        private BaseModulizerUI mNodisizer;
        private NodisizerEditor mNodisizerEditor;

        public NodisizerAreaHandler(BaseModulizerUI nodisizer, double sequenceObjectHeight)
            : base(sequenceObjectHeight)
        {
            if (nodisizer == null)
            {
                throw new ArgumentNullException(nameof(nodisizer));
            }

            mNodisizer = nodisizer;

            MaxHeightIndexes = 100;

            CameraFillObjectMarginalHeight = 1;

            mNodisizerEditor.Initialize(mNodisizer);

            CurrentSnap = Snaps.NoSnap;

            RenderSimpleRows = false;
        }
        
        public override ITransformSequenceObject GetSequencerObject(int index)
        {
            BaseModule module = mNodisizer.CurrentModuleContainer.SubModules[index];

            return mNodisizer.GetModulePanel(module);
        }

        public override int SequencerObjectCount()
        {
            return mNodisizer.CurrentModuleContainer.SubModules.Count;
        }

        public override void MouseHover(double x, double y)
        {

        }

        public override void MouseLeave()
        {

        }

        protected override void OnAreaRender()
        {
            // Render module panels.
            int count = SequencerObjectCount();
            for (int i = 0; i < count; i++)
            {
                ModulePanel panel = (ModulePanel)GetSequencerObject(i);
            
                // NOTE: Windows Phone 8 seems to get null sometimes, maybe some threading problem?
                if (panel != null)
                {
                    panel.X = GetScreenPositionX(panel.Start);
                    panel.Y = GetScreenPositionY(panel.HeightPosition);
                    panel.Width = GetScreenPositionX(panel.Start + panel.DisplayLength) - panel.X;
                    panel.Height = GetScreenPositionY(panel.HeightPosition + panel.DisplayHeight) - panel.Y;
                    //panel.Width = GetScreenPositionX(panel.DisplayLength);
                    //panel.Height = GetScreenPositionY(panel.DisplayHeight);

                    panel.Render(GraphicsHost.GraphicsDevice, ScreenParent);
                }
            }
        }

        private Vector2 mMousePosition;
        private TransformModuleCommand mOngoingReferenceTransforming;
        private double mTrackXStart = 0;
        private double mTrackYStart = 0;
        private Button left;
        private Button move;
        private Button right;
        private Button upDown;

        protected override void OnSelectorInitialized()
        {
            const double BUTTON_SIZE = 80;
            Color buttonColor = Color.DarkGray;
            buttonColor.A = 180;

            left = new Button(GuiManager);
            left.SetButtonColor(buttonColor);
            left.setTextureIcon("Images/Icons/arrow_left.png");
            left.Events.OnMousePressing += delegate (object sender, double x, double y, int id)
            {
                // Get mouse down track position.
                mTrackXStart = GetTrackPositionX(x, false);
                mTrackYStart = GetTrackPositionY(y);

                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                BeginProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseStart(x, y);
            };
            left.Events.OnMouseMoving += delegate (object sender, double x, double y, int id)
            {
                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                ProcessScreenMovement(Editor.SequencerArguments);

                mOngoingReferenceTransforming.DeltaMoveLeft(
                    Snap(GetTrackPositionX(x, false) - mTrackXStart));
                Editor.SelectorHandler.Update();
            };
            left.Events.OnMouseUnpressing += delegate
            {
                EndProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseRelease();
            };
            left.Width = BUTTON_SIZE;
            left.Height = BUTTON_SIZE;
            AddSelectorButton(left, 0);

            move = new Button(GuiManager);
            move.SetButtonColor(buttonColor);
            move.setTextureIcon("Images/Icons/move.png");
            move.Events.OnMousePressing += delegate (object sender, double x, double y, int id)
            {
                // Get mouse down track position.
                mTrackXStart = GetTrackPositionX(x, false);
                mTrackYStart = GetTrackPositionY(y);

                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                BeginProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseStart(x, y);
            };
            move.Events.OnMouseMoving += delegate (object sender, double x, double y, int id)
            {
                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                ProcessScreenMovement(Editor.SequencerArguments);

                // Move a reference
                mOngoingReferenceTransforming.DeltaMove(
                    Snap(GetTrackPositionX(x, false) - mTrackXStart),
                    GetTrackPositionY(y) - mTrackYStart);
                Editor.SelectorHandler.Update();
            };
            move.Events.OnMouseUnpressing += delegate
            {
                EndProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseRelease();
            };
            move.Width = BUTTON_SIZE;
            move.Height = BUTTON_SIZE;
            AddSelectorButton(move, 0);

            right = new Button(GuiManager);
            right.SetButtonColor(buttonColor);
            right.setTextureIcon("Images/Icons/arrow_right.png");
            right.Events.OnMousePressing += delegate (object sender, double x, double y, int id)
            {
                // Get mouse down track position.
                mTrackXStart = GetTrackPositionX(x, false);
                mTrackYStart = GetTrackPositionY(y);

                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                BeginProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseStart(x, y);
            };
            right.Events.OnMouseMoving += delegate (object sender, double x, double y, int id)
            {
                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                ProcessScreenMovement(Editor.SequencerArguments);

                mOngoingReferenceTransforming.DeltaMoveRight(
                    Snap(GetTrackPositionX(x, false) - mTrackXStart));
                Editor.SelectorHandler.Update();
                UpdateSelectorButtons();
            };
            right.Events.OnMouseUnpressing += delegate
            {
                EndProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseRelease();
            };
            right.Width = BUTTON_SIZE;
            right.Height = BUTTON_SIZE;
            AddSelectorButton(right, 0);

            Button unselect = new Button(GuiManager);
            unselect.SetButtonColor(buttonColor);
            unselect.setTextureIcon("Images/Icons/icon_unmark.png");
            unselect.OnClicked += delegate
            {
                Editor.SelectorHandler.ClearSelection();
            };
            unselect.Width = BUTTON_SIZE;
            unselect.Height = BUTTON_SIZE;
            AddSelectorButton(unselect, 1);


            upDown = new Button(GuiManager);
            upDown.SetButtonColor(buttonColor);
            upDown.setTextureIcon("Images/Icons/arrow_down.png");
            upDown.Events.OnMousePressing += delegate (object sender, double x, double y, int id)
            {
                // Get mouse down track position.
                mTrackXStart = GetTrackPositionX(x, false);
                mTrackYStart = GetTrackPositionY(y);

                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                BeginProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseStart(x, y);
            };
            upDown.Events.OnMouseMoving += delegate (object sender, double x, double y, int id)
            {
                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                ProcessScreenMovement(Editor.SequencerArguments);

                mOngoingReferenceTransforming.DeltaMoveUpDown(
                    GetTrackPositionY(y) - mTrackYStart);
                Editor.SelectorHandler.Update();
                UpdateSelectorButtons();
            };
            upDown.Events.OnMouseUnpressing += delegate
            {
                EndProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseRelease();
            };
            upDown.Width = BUTTON_SIZE;
            upDown.Height = BUTTON_SIZE;
            AddSelectorButton(upDown, 1);

            Button duplicate = new Button(GuiManager);
            duplicate.SetButtonColor(buttonColor);
            duplicate.setTextureIcon("Images/Icons/icon_x2.png");
            duplicate.OnClicked += delegate
            {
                var selectedReferences = Editor.SelectorHandler.SelectedObjects;
                DuplicateModules(selectedReferences);
            };
            duplicate.Width = BUTTON_SIZE;
            duplicate.Height = BUTTON_SIZE;
            AddSelectorButton(duplicate, 1);

            Button goInside = new Button(GuiManager);
            goInside.SetButtonColor(buttonColor);
            goInside.setTextureIcon("Images/Icons/zoomin.png");
            goInside.OnClicked += delegate
            {
                var selectedReferences = Editor.SelectorHandler.SelectedObjects;

                ModulePanel module = selectedReferences[0];
                
                mNodisizer.GoInto(module.Module);
            };
            goInside.Width = BUTTON_SIZE;
            goInside.Height = BUTTON_SIZE;
            AddSelectorButton(goInside, 1, delegate (Panel sender)
            {
                // Only one can be selected.
                if (Editor.SelectorHandler.SelectedObjects.Count == 1)
                {
                    var selectedReferences = Editor.SelectorHandler.SelectedObjects;

                    ModulePanel module = selectedReferences[0];

                    // We can only go inside a container module.
                    if (module.Module is Modules.Container.IModuleContainer)
                    {
                        return true;
                    }
                }

                return false;
            });

            Button makeGroup = new Button(GuiManager);
            makeGroup.SetButtonColor(buttonColor);
            //makeGroup.setTextureIcon("Images/Icons/icon_x2.png");
            makeGroup.setText("Group");
            makeGroup.OnClicked += delegate
            {
                var selectedReferences = Editor.SelectorHandler.SelectedObjects;

                mNodisizer.MakeGroup(selectedReferences);
            };
            makeGroup.Width = BUTTON_SIZE;
            makeGroup.Height = BUTTON_SIZE;
            AddSelectorButton(makeGroup, 1, delegate (Panel sender)
            {
                return Editor.SelectorHandler.SelectedObjects.Count > 1;
            });

            Button remove = new Button(GuiManager);
            remove.SetButtonColor(buttonColor);
            remove.setTextureIcon("Images/Icons/delete.png");
            remove.OnClicked += delegate
            {
                var selectedModules = Editor.SelectorHandler.SelectedObjects;
                RemoveModules(selectedModules);
                Editor.SelectorHandler.Update();
            };
            remove.Width = BUTTON_SIZE;
            remove.Height = BUTTON_SIZE;
            AddSelectorButton(remove, 1);
        }

        public IEnumerable<BaseModule> DuplicateModules(IEnumerable<ModulePanel> modulePanels)
        {
            if (modulePanels == null)
            {
                throw new ArgumentNullException(nameof(modulePanels));
            }

            bool wasProtectedModule = false;

            List<BaseModule> modules = new List<BaseModule>(modulePanels.Count());
            foreach (var modulePanel in modulePanels)
            {
                if (!mNodisizer.Modulizer.mModuleManager.IsProtectedModule(modulePanel.Module))
                {
                    modules.Add(modulePanel.Module);
                }
                else
                {
                    wasProtectedModule = true;
                }
            }

            DuplicateModuleCommand command = new DuplicateModuleCommand(this, mNodisizer, modules);
            mNodisizer.UndoRedo.AddAndRunCommand(command);

            if (wasProtectedModule)
            {
                SharpDepend.Manager.DialogManager.Show("Can not duplicate protected module.", "Duplicate module", SharpDepend.DialogIcon.Warning);
            }

            return command.NewModules;
        }

        private void RemoveModules(IEnumerable<ModulePanel> modulePanels)
        {
            if (modulePanels == null)
            {
                throw new ArgumentNullException(nameof(modulePanels));
            }

            bool wasProtectedModule = false;

            EmptyCommand command = new EmptyCommand("Removed modules.");
            foreach (var modulePanel in modulePanels)
            {
                if (!mNodisizer.Modulizer.mModuleManager.IsProtectedModule(modulePanel.Module))
                {
                    RemoveModuleCommand com = new RemoveModuleCommand(this, mNodisizer, modulePanel.Module);
                    command.Merge(com);
                }
                else
                {
                    wasProtectedModule = true;
                }
            }
            
            mNodisizer.UndoRedo.AddAndRunCommand(command);

            if (wasProtectedModule)
            {
                SharpDepend.Manager.DialogManager.Show("Can not remove protected module.", "Remove module", SharpDepend.DialogIcon.Warning);
            }
        }

        private void SelectorButtonMouseStart(double x, double y)
        {
            mMousePosition.X = x;
            mMousePosition.Y = y;
            mOngoingReferenceTransforming = new TransformModuleCommand(HostContext.Application, mNodisizer, this, Editor.SelectorHandler.SelectedObjects);
        }

        private void SelectorButtonMouseRelease()
        {
            if (mOngoingReferenceTransforming != null)
            {
                if (mOngoingReferenceTransforming.FinishTransform())
                {
                    mNodisizer.UndoRedo.AddAndRunCommand(mOngoingReferenceTransforming);
                }
                mOngoingReferenceTransforming = null;
            }
        }

        protected override void OnUpdatedAfterMouseMove()
        {
            Editor.SelectorHandler.Update();
        }

        protected override void OnCameraAnimation()
        {
            // Calls every frame when camera is animating.
            Editor.SelectorHandler.Update();
        }

        protected override void RenderTimelines()
        {
            // Override this to remove rendering of the timelines.
        }

        protected override BaseSequencerEditor<TrackAreaArgs, ModulePanel, BasicSequenceState> OnCreateEditorInstance()
        {
            mNodisizerEditor = new NodisizerEditor(this, SequenceObjectHeight);
            return mNodisizerEditor;
        }
        
        internal class NodisizerEditor : BaseSequencerEditor<TrackAreaArgs, ModulePanel, BasicSequenceState>
        {
            private BaseModulizerUI mNodisizer;
            private NodisizerAreaHandler mBaseArea;

            public NodisizerEditor(NodisizerAreaHandler baseArea, double sequenceObjectHeight)
                : base(baseArea, sequenceObjectHeight)
            {
                if (baseArea == null)
                {
                    throw new ArgumentNullException(nameof(baseArea));
                }
                mBaseArea = baseArea;
            }

            internal void Initialize(BaseModulizerUI nodisizer)
            {
                if (nodisizer == null)
                {
                    throw new ArgumentNullException(nameof(nodisizer));
                }
                mNodisizer = nodisizer;
            }
            
            protected override IEnumerable<ModulePanel> OnCloned(IEnumerable<ModulePanel> objects)
            {
                IEnumerable<BaseModule> modules = mBaseArea.DuplicateModules(objects);

                List<ModulePanel> panels = new List<ModulePanel>();
                int count = mBaseArea.SequencerObjectCount();
                for (int i = 0; i < count; i++)
                {
                    ModulePanel panel = (ModulePanel)mBaseArea.GetSequencerObject(i);

                    if (modules.Contains(panel.Module))
                    {
                        panels.Add(panel);
                    }
                }

                return panels;
            }

            protected override ModulePanel OnCreatedObject(double x, double y)
            {
                BaseModule module =  mNodisizer.CreateModule();

                if (module != null)
                {
                    Vector4 bound = new Vector4(x, y, x + 100, y + 1);

                    AddModuleCommand command = new AddModuleCommand(mNodisizer, bound, module);
                    mNodisizer.UndoRedo.AddAndRunCommand(command);
                    
                    return mNodisizer.GetModulePanel(command.AddedModule);
                }
                else
                {
                    return null;
                }
            }

            protected override void OnPickedSequeneObject(ModulePanel sequenceObject)
            {
                mNodisizer.UpdateModuleForEdit(sequenceObject.Module);
            }

            protected override BaseTransformCommand<ModulePanel, BasicSequenceState> OnCreatedTransforCommandObject(IEnumerable<ModulePanel> objects)
            {
                return new TransformModuleCommandV1(Application, mNodisizer, mBaseArea, objects);
            }

            protected override void OnInitialized()
            {

            }

            protected override void OnRemovedObject(ModulePanel obj)
            {
                throw new NotImplementedException();
            }
        }
    }
}
